#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
This module allows to select the virtual midi port to send to,
and then starts a bluetooth server over RFCOMM to get the input from the phone
"""

import sys
from time import sleep
import thread
from optparse import OptionParser

try:
    import bluetooth as bt
except ImportError:
    raise Exception("You need the bluetooth library for python installed (pybluez)")

try:
    from rtmidi import RtMidiOut, RtMidiIn, RtError
except ImportError:
    raise Exception("You need the rtmidi library for python installed (rtmidi)")


#bluetooth uuid
UUID = "ce0ab4c0-0986-11df-8a39-0800200c9a66"

stopThreads = False

def listMidi(midiIn, midiOut):
    #OUT
    print "Enumerating output midi ports..."
    nbPorts = midiOut.getPortCount()
    print "Number of output midi ports detected: %d"%nbPorts
    if nbPorts>0:
        print "output midi ports available:"
        for i in xrange(nbPorts):
            print "[%d]\t%s"%(i,midiOut.getPortName(i))
    else:
        print "No output midi port available on your system, please install a virtual driver first."
    #IN
    print "Enumerating input midi ports:"
    nbPorts = midiIn.getPortCount()
    print "Number of input midi ports detected: %d"%nbPorts
    if nbPorts>0:
        print "input midi ports available:"
        for i in xrange(nbPorts):
            print "[%d]\t%s"%(i,midiIn.getPortName(i))
    else:
        print "No input midi port available on your system, please install a virtual driver first."

def setupMidi(midiPortOut, midiPortIn):
    """
    Open midi ports for output and input
    return the rtmidi in and out out object
    """
    print "================== MIDI SETUP =================="
    if midiPortOut:
        midiOut.openPort(midiPortOut)
    else:
        midiPortOut = -1
        while not midiPortOut in range(midiOut.getPortCount()):
            try:midiPortOut = int(raw_input("enter the port number you wish to connect the midi output "))
            except:midiPortOut=-1
        midiOut.openPort(midiPortOut)
    if midiPortIn:
        midiIn.openPort(midiPortIn)
    else:
        midiPortIn = -1
        while not midiPortIn in range(midiIn.getPortCount()):
            try:midiPortIn = int(raw_input("enter the port number you wish to connect the midi input "))
            except:midiPortIn=-1
        midiIn.openPort(midiPortIn)
    print "============== MIDI SETUP FINISHED ============"
    return midiOut, midiIn


def handleMidiFromPhone(midiOut, midiIn):
    """
    phone->touch->bt->computer->midi
    """
    print "\n============= BLUETOOTH SERVER LAUNCH ===========\n"
    server_sock=bt.BluetoothSocket(bt.RFCOMM)
    server_sock.bind(("",bt.PORT_ANY))
    server_sock.listen(1)
    
    port = server_sock.getsockname()[1]
    
    bt.advertise_service(server_sock, "Server",
                         service_id = UUID,
                         service_classes = [ UUID, bt.SERIAL_PORT_CLASS ],
                         profiles = [ bt.SERIAL_PORT_PROFILE ])
    
    print "\n[BLUETOOTH]Waiting for bluetooth connection from the device\n"
    client_sock, client_info = server_sock.accept()
    print "[BLUETOOTH]Phone connected: ", client_info
    thread.start_new_thread(handleMidiToPhone, (client_sock, midiIn))
    try:
        global stopThreads
        data = []
        while not stopThreads:
            data += client_sock.recv(255)
            if len(data) == 0: break
            else:
                #print "[MIDIOUT]received : %s from device"%(data)
                if len(data)<3:
                    continue
                else:
                    i=0
                    print "[MIDIOUT]received : %s from device"%(data)
                    while i+3<=len(data):
                        midiOut.sendMessage(ord(data[i]), ord(data[i+1]), ord(data[i+2]))
                        i+=3
                    data= []
    except IOError, msg:
        print msg
    print "[MIDIOUT]disconnected"
    
    client_sock.close()
    server_sock.close()
    print "[MIDIOUT]all sockets closed"

def handleMidiToPhone(socket, midiIn):
    """
    computer-midi->bt->phone
    """
    try:
        global stopThreads
        while not stopThreads:
            #print "waiting for message"
            data = midiIn.getMessage()
            if data:
                d1, d2, d3 = data
                print "received %c, %c, %c"%(d1,d2,d3)
                socket.write("%c%c%c"%(chr(d1), chr(d2), chr(d3)))
            else:
                try:sleep(0.01)
                except Exception, msg : print "can't sleep in here!!! ",msg
    except IOError, msg:
        print msg
    print "[MIDIOUT]disconnected"
    
    client_sock.close()
    server_sock.close()
    print "[MIDIOUT]all sockets closed"


if __name__=="__main__":
    usage = "usage: %prog [-o1] [-i1]"
    parser = OptionParser(usage=usage, version="%prog 2.5")
    parser.add_option("-o", "--midi-port-out", dest="midiPortOut", type="int",
                  help="OUTPUT Midi port number to use")
    parser.add_option("-i", "--midi-port-in", dest="midiPortIn", type="int",
                  help="INPUT Midi port number to use")
    (options, args) = parser.parse_args()
    
    midiOut = RtMidiOut()
    midiIn = RtMidiIn()
    
    listMidi(midiIn, midiOut)
    
    setupMidi(options.midiPortOut, options.midiPortIn)
    thread.start_new_thread(handleMidiFromPhone, (midiOut, midiIn))
    raw_input("\nPress enter to stop the server and quit")
    #TODO: terminate the bt server cleanly... (or not)
    stopThreads = True